﻿using System;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Metadata.Edm;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml;
using System.Xml.Xsl;
using System.Windows.Forms;
using Microsoft.Samples.Data.eSqlBlast;


namespace Microsoft.Samples.Data.eSqlBlast.WinShell
{
    public partial class Main : Form
    {
        private const string EntityConnectionStringPattern =
            "Provider={0}; \r\n" +
            "Provider Connection String='{1}'; \r\n" +
            "Metadata={2}; \r\n";
        private const string StatusConnectionConnected = "Use the Model tab to browse the model and the Query tab to query the model.";
        private const string StatusConnectionDisconnected = "Enter connection string components and click CONNECT.";
        private const string StatusModel = "Browse a model (.csdl) file and click RENDER.";
        private const string StatusQueryConnected = "Enter one or more Entity SQL queries separated with ; and click EXECUTE.";
        private const string StatusQueryDisconnected = "In order to execute queries, you need to establish a connection through the Connection tab first.";
        private const string StatusQueryResults = "See the results from the execution in the Results tab.";
        private const string StatusResultsConnected = "To execute more Entity SQL queries, go back to the Query tab.";
        private const string StatusResultsDisconnected = StatusQueryDisconnected;


        private Session blastSession = null;
        private StringWriter blastOutput = new StringWriter();
        private string transformedCsdlPath = Path.GetTempFileName() + ".htm";
        private string rawResultsPath = Path.GetTempFileName() + ".xml";
        private string transformedResultsPath = Path.GetTempFileName() + ".htm";
        private bool ignoreTextChange = false;
        private string intellisenseText = "";
        private int intellisenseSelectionStart = 0;
        private float zoomFactor = 1;

        
        public Main()
        {
            InitializeComponent();
        }


        #region Connection tab
        private void connectionTab_Enter(object sender, EventArgs e)
        {
            this.UpdateEntityConnectionString(sender, e);

            if (this.blastSession == null)
            {
                this.blastSession = new Session();
            }

            if (this.blastSession.IsConnected)
            {
                this.tipStatus.Text = StatusConnectionConnected;
            }
            else
            {
                this.tipStatus.Text = StatusConnectionDisconnected;
            }
        }
        
        private void addFilesButton_Click(object sender, EventArgs e)
        {
            this.openFileDialog.Filter = "EDM Files (*.csdl;*.msl;*.ssdl)|*.csdl;*.msl;*.ssdl";
            this.openFileDialog.Multiselect = true;

            if (this.openFileDialog.ShowDialog() == DialogResult.OK)
            {
                this.metadataListBox.Items.AddRange(this.openFileDialog.FileNames);
                this.UpdateEntityConnectionString(null, null);
            }
        }

        private void addFolderButton_Click(object sender, EventArgs e)
        {
            if (this.folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                this.metadataListBox.Items.Add(this.folderBrowserDialog.SelectedPath);
                this.UpdateEntityConnectionString(null, null);
            }
        }

        private void removeButton_Click(object sender, EventArgs e)
        {
            if (this.metadataListBox.SelectedIndex >= 0)
            {
                this.metadataListBox.Items.RemoveAt(this.metadataListBox.SelectedIndex);
                this.UpdateEntityConnectionString(null, null);
            }
        }

        private void connectButton_Click(object sender, EventArgs e)
        {
            try
            {
                this.connectButton.Enabled = false;

                // Disconnect first
                this.blastSession.Disconnect();
                this.connectionStatus.Text = "Disconnected";
                this.connectionStatus.ForeColor = Color.Red;
                this.tipStatus.Text = StatusConnectionDisconnected;

                // (Re-)connect
                this.blastSession.Connect(this.entityConnectionString.Text);
                this.connectionStatus.Text = "Connected";
                this.connectionStatus.ForeColor = Color.Green;
                this.tipStatus.Text = StatusConnectionConnected;

                this.connectButton.Enabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void UpdateEntityConnectionString(object sender, EventArgs e)
        {
            this.entityConnectionString.Text = string.Format(EntityConnectionStringPattern,
                                                            this.provider.Text,
                                                            this.providerConnectionString.Text,
                                                            this.GetMetadata());
        }

        private string GetMetadata()
        {
            StringBuilder metadataBuilder = new StringBuilder();
            bool isFirstTime = true;

            foreach (string item in this.metadataListBox.Items)
            {
                if (isFirstTime)
                {
                    isFirstTime = false;
                }
                else
                {
                    metadataBuilder.Append("|");
                }
                metadataBuilder.Append(item);
            }

            return metadataBuilder.ToString();
        }
        #endregion


        #region Model tab
        private void modelTab_Enter(object sender, EventArgs e)
        {
            this.tipStatus.Text = StatusModel;
        }

        private void csdlBrowseButton_Click(object sender, EventArgs e)
        {
            this.openFileDialog.Filter = "EDM Files (*.csdl)|*.csdl";
            this.openFileDialog.Multiselect = false;

            if (this.openFileDialog.ShowDialog() == DialogResult.OK)
            {
                this.csdlPath.Text = this.openFileDialog.FileName;
            }
        }

        private void renderButton_Click(object sender, EventArgs e)
        {
            try
            {
                XslCompiledTransform xslt = new XslCompiledTransform();
                xslt.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "csdl.xsl"));
                xslt.Transform(this.csdlPath.Text, this.transformedCsdlPath);

                this.modelBrowser.Navigate(this.transformedCsdlPath);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        #endregion


        #region Query tab
        private void queryTab_Enter(object sender, EventArgs e)
        {
            this.executeButton.Enabled = this.blastSession.IsConnected;
            if (this.blastSession.IsConnected)
            {
                this.tipStatus.Text = StatusQueryConnected;
            }
            else
            {
                this.tipStatus.Text = StatusQueryDisconnected;
            }

            this.queryEdit.Focus();
        }

        private void queryEdit_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.F5)
            {
                e.Handled = true;
                this.executeButton_Click(null, null);
            }
        }

        private void executeButton_Click(object sender, EventArgs e)
        {
            this.executeButton.Enabled = false;
            bool ok = false;

            try
            {
                StreamWriter xmlOutput = new StreamWriter(this.rawResultsPath);

                this.blastSession.ResultVisitor = new XmlVisitor(xmlOutput, null, this.fullExceptions.Checked);
                this.blastSession.Execute(this.queryEdit.Text);
                xmlOutput.Close();

                string resultsPath = this.rawResultsPath;
                if (!this.rawResults.Checked)
                {
                    XsltSettings xsltSettings = new XsltSettings();
                    xsltSettings.EnableScript = true;

                    XslCompiledTransform xslt = new XslCompiledTransform();
                    xslt.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "default.xsl"), xsltSettings, new XmlUrlResolver());
                    xslt.Transform(this.rawResultsPath, this.transformedResultsPath);
                    
                    resultsPath = this.transformedResultsPath;
                }

                this.resultsBrowser.Navigate(resultsPath);

                this.tipStatus.Text = StatusQueryResults;
                ok = true;
            }
            finally
            {
                this.executeButton.Enabled = true;
            }

            if (ok)
            {
                this.MultiView.SelectTab(this.resultsTab);
            }
        }

        private void zoom_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.ChangeZoomFactor(this.zoom.SelectedItem as string);
        }

        private void zoom_TextUpdate(object sender, EventArgs e)
        {
            this.ChangeZoomFactor(this.zoom.Text);
        }

        private void ChangeZoomFactor(string zoomFactorText)
        {
            Regex zoomPattern = new Regex(@"^\s*\d+");
            Match zoomMatch = zoomPattern.Match(zoomFactorText);

            if (zoomMatch.Success)
            {
                int zoomPercent = int.Parse(zoomMatch.Value);
                if (zoomPercent >= 50)
                {
                    this.zoomFactor = (float)zoomPercent/100;
                    this.queryEdit.Font = new Font("Courier New", 9 * this.zoomFactor);
                }
            }
        }
        #endregion


        #region Results tab
        private void resultsTab_Enter(object sender, EventArgs e)
        {
            this.executeButton.Enabled = this.blastSession.IsConnected;
            if (this.blastSession.IsConnected)
            {
                this.tipStatus.Text = StatusResultsConnected;
            }
            else
            {
                this.tipStatus.Text = StatusResultsDisconnected;
            }
        }
        #endregion


        #region About tab
        private void navigateLink(object sender, LinkLabelLinkClickedEventArgs e)
        {
            Process.Start((sender as LinkLabel).Text);
        }
        #endregion


        #region Realtime Intellisense
        private readonly string[] Keywords =
        {
            "SELECT", "VALUE", "FROM", "WHERE", "ORDER", "ASC", "DESC", "COLLATE", "GROUP", "HAVING", "AS", "TOP", "SKIP", "LIMIT", 
            "USING", "ALL", "DISTINCT", "ROW","MULTISET", "AND", "OR", "NOT", "IS", "NULL", "LIKE", "ESCAPE", "BETWEEN", 
            "REF", "DEREF", "CREATEREF", "KEY", "CASE", "END", "WHEN", "THEN", "ELSE", "TREAT", "CAST", "OF", "OFTYPE", "ONLY", 
            "UNION", "INTERSECT", "EXCEPT", "OVERLAPS", "IN", "EXISTS", "ANYELEMENT", "SET", "FLATTEN", "NAVIGATE", "BY", "ON",
            "LEFT", "RIGHT", "OUTER", "INNER", "FULL", "JOIN", "CROSS", "APPLY", "NULL", "TRUE", "FALSE", "DATETIME", "BINARY", "GUID" 
        };

        private readonly string[] Functions =
        {
            "SUM", "AVG", "COUNT", "BIGCOUNT", "MIN", "MAX", "STDEV",
            "TRIM", "LTRIM", "RTRIM", "CONCAT", "LENGTH", "SUBSTRING", "REPLACE", "LEFT", "RIGHT", "INDEXOF",
            "TOUPPER", "TOLOWER", "REVERSE", "YEAR", "MONTH", "DAY", "HOUR", "MINUTE", "SECOND",
            "GETDATE", "GETUTCDATE", "DATEDIFF", "DATEADD", 
            "ROUND", "FLOOR", "CEILING", "ABS", "BITWISENOT", "BITWISEAND", "BITWISEOR", "BITWISEXOR", "NEWGUID"
        };

        private enum SpecialWord
        {
            None,
            From,
            Entity,
        };

        private const int IntellisenseFontSize = 9;


        private void Intellisense(object sender, EventArgs e)
        {
            if (this.ignoreTextChange)
                return;

            this.intellisenseTimer.Stop();

            int selectionStart = this.queryEdit.SelectionStart;
            int selectionLength = this.queryEdit.SelectionLength;

            if (selectionStart > 0)
            {
                if (char.IsLetter(this.queryEdit.Text, selectionStart - 1))
                {
                    int wordBeforeStart = this.GetWordBeforeStart(selectionStart);
                    int wordAfterEnd = this.GetWordAfterEnd(selectionStart);
                    this.FormatWord(wordBeforeStart, wordAfterEnd - wordBeforeStart, selectionStart, selectionLength);
                }
                else
                {
                    SpecialWord special;

                    int wordBeforeStart = this.GetWordBeforeStart(selectionStart);
                    special = this.FormatWord(wordBeforeStart, selectionStart - wordBeforeStart, selectionStart, selectionLength);

                    int wordAfterEnd = this.GetWordAfterEnd(selectionStart);
                    this.FormatWord(selectionStart, wordAfterEnd - selectionStart, selectionStart, selectionLength);

                    if (special == SpecialWord.From && char.IsWhiteSpace(this.queryEdit.Text, selectionStart - 1))
                    {
                        this.SuggestEntityContainer();
                    }
                    else if (this.queryEdit.Text.Substring(selectionStart - 1, 1) == ".")
                    {
                        int word2BeforeStart = this.GetWordBeforeStart(selectionStart - 1);
                        string word = this.queryEdit.Text.Substring(word2BeforeStart, selectionStart - word2BeforeStart - 1).Trim();

                        if (this.IsEntityContainer(word))
                        {
                            this.SuggestEntitySetOrType(word);
                        }
                    }
                }
            }

            this.intellisenseTimer.Start();
        }


        private void SuggestEntityContainer()
        {
            if (this.blastSession != null && this.blastSession.Connection != null)
            {
                List<string> names = new List<string>();
                MetadataWorkspace workspace = this.blastSession.Connection.GetMetadataWorkspace();
                foreach (EntityContainer container in workspace.GetItems<EntityContainer>(DataSpace.CSpace))
                {
                    names.Add(container.Name);
                }

                this.OpenIntellisenseList(names);
            }
        }


        private void SuggestEntitySetOrType(string containerName)
        {
            if (this.blastSession != null && this.blastSession.Connection != null)
            {
                List<string> names = new List<string>();
                MetadataWorkspace workspace = this.blastSession.Connection.GetMetadataWorkspace();
                EntityContainer container;

                if (workspace.TryGetEntityContainer(containerName, true, DataSpace.CSpace, out container))
                {
                    foreach (EntitySetBase setBase in container.BaseEntitySets)
                    {
                        names.Add(setBase.Name);
                    }
                }

                foreach (EntityType type in workspace.GetItems<EntityType>(DataSpace.CSpace))
                {
                    if (names.IndexOf(type.Name) < 0)
                    {
                        names.Add(type.Name);
                    }
                }

                this.OpenIntellisenseList(names);
            }
        }


        private void OpenIntellisenseList(IList<string> names)
        {
            int maxLength = 0;
            int maxItems = 10;

            if (names.Count > 0)
            {
                this.intellisenseList.Items.Clear();
                foreach (string name in names)
                {
                    this.intellisenseList.Items.Add(name);
                    if (name.Length > maxLength)
                    {
                        maxLength = name.Length;
                    }
                }

                if (names.Count < maxItems)
                {
                    maxItems = names.Count;
                }

                this.intellisenseText = "";
                this.intellisenseSelectionStart = this.queryEdit.SelectionStart;

                POINT caret = new POINT();
                int success = GetCaretPos(ref caret);

                this.intellisenseList.SetSelected(0, true);
                this.intellisenseList.Parent = this.queryEdit;
                this.intellisenseList.Location = new Point(caret.X, caret.Y + 2*IntellisenseFontSize);
                this.intellisenseList.Size = new Size(maxLength * IntellisenseFontSize + 10,
                                                        maxItems * this.intellisenseList.ItemHeight + 10);
                this.intellisenseList.Show();
                this.intellisenseList.Select();
                this.ignoreTextChange = true;
            }
        }


        private void CloseIntellisenseList(bool select)
        {
            if (select)
            {
                int selectionStart = this.queryEdit.SelectionStart;
                this.queryEdit.Select(this.intellisenseSelectionStart, selectionStart - this.intellisenseSelectionStart);

                Clipboard.Clear();
                Clipboard.SetData(DataFormats.Text, this.intellisenseList.SelectedItem.ToString());
                this.queryEdit.Paste();
            }

            this.intellisenseList.Hide();
            this.queryEdit.Select();
            this.ignoreTextChange = false;
        }
               

        private int GetWordBeforeStart(int start)
        {
            int i = 0;

            if (start > 0)
            {
                if (char.IsWhiteSpace(this.queryEdit.Text, start - 1))
                {
                    start--;
                }

                for (i = start - 1; i >= 0 && char.IsLetter(this.queryEdit.Text, i); i--) ;
                i++;
            }
            
            return i;
        }


        private int GetWordAfterEnd(int start)
        {
            int i;
            int l = this.queryEdit.Text.Length;

            for (i = start; i < l && char.IsLetter(this.queryEdit.Text, i); i++) ;

            return i;
        }


        private bool IsKeyword(string word)
        {
            return IsMember(word, Keywords);
        }


        private bool IsFunction(string word)
        {
            return IsMember(word, Functions);
        }


        private bool IsMember(string word, string[] array)
        {
            word = this.StripIdentifier(word);
            string upperWord = word.ToUpperInvariant();

            foreach (string member in array)
            {
                if (upperWord == member)
                {
                    return true;
                }
            }

            return false;
        }


        private bool IsEntityContainer(string word)
        {
            bool isEntityContainer = false;
            word = this.StripIdentifier(word);

            if (this.blastSession != null && this.blastSession.Connection != null)
            {
                MetadataWorkspace workspace = this.blastSession.Connection.GetMetadataWorkspace();
                foreach (EntityContainer container in workspace.GetItems<EntityContainer>(DataSpace.CSpace))
                {
                    if (container.Name.Equals(word, StringComparison.InvariantCultureIgnoreCase))
                    {
                        isEntityContainer = true;
                        break;
                    }
                }
            }

            return isEntityContainer;
        }


        private bool IsEntitySet(string word)
        {
            bool isEntitySet = false;
            word = this.StripIdentifier(word);

            if (this.blastSession != null && this.blastSession.Connection != null)
            {
                MetadataWorkspace workspace = this.blastSession.Connection.GetMetadataWorkspace();
                foreach (EntityContainer container in workspace.GetItems<EntityContainer>(DataSpace.CSpace))
                {
                    foreach (EntitySetBase setBase in container.BaseEntitySets)
                    {
                        if (setBase.Name == word)
                        {
                            isEntitySet = true;
                            break;
                        }
                    }
                }
            }

            return isEntitySet;
        }


        private string StripIdentifier(string word)
        {
            return word.TrimStart('[').TrimEnd(']');
        }


        private void intellisenseList_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\t' || e.KeyChar == '\n' || e.KeyChar == '\r')
            {
                this.CloseIntellisenseList(true);
            }
            else if (e.KeyChar == '\u001B')
            {
                this.CloseIntellisenseList(false);
            }
            else if ('\u0020' <= e.KeyChar && e.KeyChar <= '\u007E')
            {
                this.intellisenseText += e.KeyChar;
                this.UpdateIntellisenseText();
            }
            else if (e.KeyChar == '\b' && this.intellisenseText.Length > 0)
            {
                this.intellisenseText = this.intellisenseText.Substring(0, this.intellisenseText.Length - 1);
                this.UpdateIntellisenseText();
            }

            e.Handled = true;
        }


        private void UpdateIntellisenseText()
        {
            int selectionStart = this.queryEdit.SelectionStart;
            this.queryEdit.Select(this.intellisenseSelectionStart, selectionStart - this.intellisenseSelectionStart);

            Clipboard.Clear();
            Clipboard.SetData(DataFormats.Text, this.intellisenseText);
            this.queryEdit.Paste();

            int i = this.intellisenseList.FindString(this.intellisenseText);
            if (i >= 0)
            {
                this.intellisenseList.SelectedIndex = i;
            }
        }


        private void intellisenseList_Leave(object sender, EventArgs e)
        {
            this.CloseIntellisenseList(false);
        }


        protected override bool ProcessTabKey(bool forward)
        {
            bool process = true;

            if (this.intellisenseList.Visible)
            {
                this.CloseIntellisenseList(true);

            }
            else
            {
                process = base.ProcessTabKey(forward);
            }

            return process;
        }


        private struct POINT
        {
            public int X;
            public int Y;
        };


        [DllImport("User32.dll")]
        static extern int GetCaretPos(ref POINT point);
        #endregion


        #region Format
        private delegate SpecialWord FormatMethod(int wordStart, int wordLength, int selectionStart, int selectionLength);

        private SpecialWord FormatWord(int wordStart, int wordLength, int selectionStart, int selectionLength)
        {
            SpecialWord special = SpecialWord.None;

            string word = this.queryEdit.Text.Substring(wordStart, wordLength).Trim();
            if (word.Length > 0)
            {
                if (this.IsKeyword(word))
                {
                    this.FormatKeyword(wordStart, wordLength, selectionStart, selectionLength);
                    if (word.ToUpperInvariant() == "FROM")
                    {
                        special = SpecialWord.From;
                    }
                }
                else if (this.IsFunction(word))
                {
                    this.FormatFunction(wordStart, wordLength, selectionStart, selectionLength);
                }
                else if (this.IsEntityContainer(word) || this.IsEntitySet(word))
                {
                    this.FormatIdentifier(wordStart, wordLength, selectionStart, selectionLength);
                    special = SpecialWord.Entity;
                }
                else
                {
                    this.FormatText(wordStart, wordLength);
                    this.queryEdit.Select(selectionStart, selectionLength);
                }
            }

            return special;
        }


        private SpecialWord FormatKeyword(int wordStart, int wordLength, int selectionStart, int selectionLength)
        {
            this.queryEdit.Select(wordStart, wordLength);
            this.queryEdit.SelectionColor = Color.Blue;
            this.queryEdit.SelectionFont = new Font(this.queryEdit.Font, FontStyle.Bold);

            this.FormatText(selectionStart, selectionLength);

            return SpecialWord.None;
        }


        private SpecialWord FormatFunction(int wordStart, int wordLength, int selectionStart, int selectionLength)
        {
            this.queryEdit.Select(wordStart, wordLength);
            this.queryEdit.SelectionColor = Color.Magenta;
            this.queryEdit.SelectionFont = this.queryEdit.Font;

            this.FormatText(selectionStart, selectionLength);

            return SpecialWord.None;
        }


        private SpecialWord FormatIdentifier(int wordStart, int wordLength, int selectionStart, int selectionLength)
        {
            this.queryEdit.Select(wordStart, wordLength);
            this.queryEdit.SelectionColor = Color.Brown;
            this.queryEdit.SelectionFont = this.queryEdit.Font;

            this.FormatText(selectionStart, selectionLength);

            return SpecialWord.None;
        }


        private SpecialWord FormatLiteral(int wordStart, int wordLength, int selectionStart, int selectionLength)
        {
            this.queryEdit.Select(wordStart, wordLength);
            this.queryEdit.SelectionColor = Color.Red;
            this.queryEdit.SelectionFont = this.queryEdit.Font;

            this.FormatText(selectionStart, selectionLength);

            return SpecialWord.None;
        }


        private SpecialWord FormatComment(int wordStart, int wordLength, int selectionStart, int selectionLength)
        {
            this.queryEdit.Select(wordStart, wordLength);
            this.queryEdit.SelectionColor = Color.Green;
            this.queryEdit.SelectionFont = this.queryEdit.Font;

            this.FormatText(selectionStart, selectionLength);

            return SpecialWord.None;
        }


        private void FormatText(int wordStart, int wordLength)
        {
            this.queryEdit.Select(wordStart, wordLength);
            this.queryEdit.SelectionColor = this.queryEdit.ForeColor;
            this.queryEdit.SelectionFont = this.queryEdit.Font;
        }
        #endregion


        #region Offline Intellisense
        private void OfflineIntellisense(object sender, EventArgs e)
        {
            this.intellisenseTimer.Stop();
            this.ignoreTextChange = true;

            int selectionStart = this.queryEdit.SelectionStart;
            int selectionLength = this.queryEdit.SelectionLength;

            try
            {
                string inputText = this.queryEdit.Text;
                ScriptParser parser = new ScriptParser(new eSqlIntellisense());
                parser.Parse(inputText);

                foreach (Match match in parser.AllMatches)
                {
                    if (match.Value.Length > 0)
                    {
                        this.FormatRegexMatch(match, "Keyword", this.FormatWord, selectionStart, selectionLength);
                        this.FormatRegexMatch(match, "Identifier", this.FormatIdentifier, selectionStart, selectionLength);
                        this.FormatRegexMatch(match, "Comment", this.FormatComment, selectionStart, selectionLength);
                        this.FormatRegexMatch(match, "Literal", this.FormatLiteral, selectionStart, selectionLength);
                    }
                }
            }
            finally
            {
                this.ignoreTextChange = false;
                this.queryEdit.Select(selectionStart, selectionLength);
            }
        }


        private void FormatRegexMatch(Match match, string groupName, FormatMethod formatMethod, int selectionStart, int selectionLength)
        {
            string groupValue = match.Groups[groupName].Value;
            if (groupValue.Length > 0)
            {
                int groupIndex = match.Groups[groupName].Index;
                int groupLength = match.Groups[groupName].Length;
                Debug.WriteLine(groupIndex.ToString() + ":" + groupLength.ToString() + " = [" + groupName + "] '" + groupValue + "'");
                formatMethod(groupIndex, groupLength, selectionStart, selectionLength);
            }
        }
        #endregion


    }
}
